using System;
using System.Reflection;
using NUnit.Framework;
using WCSFContrib.Extensions.EventBroker;

namespace WCSFContrib.Extensions.Tests.EventBroker
{
	[TestFixture]
	public class PublisherTests
	{
		private const string expectedTopic = "foo";
		private const string fooEventName = "FooEvent";
		private EventInfo eventInfo = typeof(PublisherTests).GetEvent(fooEventName);
		public event EventHandler FooEvent;
		public void OnFoo(object sender, EventArgs e)
		{
			if (FooEvent != null)
				FooEvent.Invoke(sender, e);
		}

		Publisher publisher;

		[SetUp]
		public void SetUp()
		{
			publisher = new Publisher(this, expectedTopic, eventInfo);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void NullInstanceFails()
		{
			publisher = new Publisher(null, expectedTopic, eventInfo);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void NullTopicFails()
		{
			publisher = new Publisher(this, null, eventInfo);
		}

		[Test]
		[ExpectedException(typeof(ArgumentException))]
		public void EmptyTopicFails()
		{
			publisher = new Publisher(this, "", eventInfo);
		}

		[Test]
		[ExpectedException(typeof(ArgumentNullException))]
		public void NullEventInfoFails()
		{
			publisher = new Publisher(this, expectedTopic, null);
		}

		[Test]
		public void PropertyTest()
		{
			Assert.AreEqual(this, publisher.Instance);
			Assert.AreEqual(expectedTopic, publisher.Topic);
			Assert.AreEqual(eventInfo, publisher.MemberInfo);
			Assert.AreEqual(eventInfo.EventHandlerType, publisher.EventHandlerType);
		}

		[Test]
		public void AddHandler()
		{
			EventArgs expectedEventArgs = EventArgs.Empty;
			object expectedSender = this;

			HandlerClass handlerClass = new HandlerClass();
			Delegate @delegate = Delegate.CreateDelegate(eventInfo.EventHandlerType, handlerClass, handlerClass.HandlerMethodName);

			publisher.AddEventHandler(@delegate);
			
			FooEvent.Invoke(expectedSender, expectedEventArgs);

			Assert.AreEqual(1, handlerClass.handlerCount);
			Assert.AreEqual(expectedSender, handlerClass.handlerSender);
			Assert.AreEqual(expectedEventArgs, handlerClass.handlerE);
		}

		[Test]
		public void AddHandlers()
		{
			EventArgs expectedEventArgs = EventArgs.Empty;
			object expectedSender = this;

			HandlerClass handlerClass = new HandlerClass();
			Delegate @delegate = Delegate.CreateDelegate(eventInfo.EventHandlerType, handlerClass, handlerClass.HandlerMethodName);

			publisher.AddEventHandler(@delegate);
			publisher.AddEventHandler(@delegate);
			
			FooEvent.Invoke(expectedSender, expectedEventArgs);

			Assert.AreEqual(2, handlerClass.handlerCount);
			Assert.AreEqual(expectedSender, handlerClass.handlerSender);
			Assert.AreEqual(expectedEventArgs, handlerClass.handlerE);
		}

		[Test]
		public void RemoveHandler()
		{
			EventArgs expectedEventArgs = EventArgs.Empty;
			object expectedSender = this;

			HandlerClass handlerClass = new HandlerClass();
			Delegate @delegate = Delegate.CreateDelegate(eventInfo.EventHandlerType, handlerClass, handlerClass.HandlerMethodName);

			publisher.AddEventHandler(@delegate);
			publisher.RemoveEventHandler(@delegate);
			
			FooEvent.Invoke(expectedSender, expectedEventArgs);

			Assert.AreEqual(0, handlerClass.handlerCount);
		}

		[Test]
		public void KillInstanceWillNotAttemptToAddHandler()
		{
			EventArgs expectedEventArgs = EventArgs.Empty;
			object expectedSender = this;

			HandlerClass handlerClass = new HandlerClass();
			Delegate @delegate = Delegate.CreateDelegate(eventInfo.EventHandlerType, handlerClass, handlerClass.HandlerMethodName);

			publisher = new Publisher(new PublisherTests(), expectedTopic, eventInfo);
			GC.Collect(); // The new PublisherTests instance from the line above is now collected.

			// Test - Adding a handler to a dead instance is unsuccessful.
			bool result = publisher.AddEventHandler(@delegate);

			OnFoo(expectedSender, expectedEventArgs);

			Assert.AreEqual(0, handlerClass.handlerCount);
			Assert.IsFalse(result);
		}

		[Test]
		public void ADeadInstanceWillNotRemoveHandler()
		{
			EventArgs expectedEventArgs = EventArgs.Empty;
			object expectedSender = this;

			HandlerClass handlerClass = new HandlerClass();
			Delegate @delegate = Delegate.CreateDelegate(eventInfo.EventHandlerType, handlerClass, handlerClass.HandlerMethodName);

			publisher = new Publisher(new PublisherTests(), expectedTopic, eventInfo);

			bool addResult = publisher.AddEventHandler(@delegate);

			GC.Collect(); // The new PublisherTests instance from above is now collected.

			// Test
			bool removeResult = publisher.RemoveEventHandler(@delegate);

			OnFoo(expectedSender, expectedEventArgs);

			Assert.AreEqual(0, handlerClass.handlerCount);
			Assert.IsTrue(addResult);
			Assert.IsFalse(removeResult);
		}

		private class HandlerClass
		{
			public int handlerCount = 0;
			public object handlerSender;
			public EventArgs handlerE;
			public string HandlerMethodName = "handler";

			private void handler(object sender, EventArgs e)
			{
				handlerCount++;
				handlerSender = sender;
				handlerE = e;
			}
		}
	}
}